package code;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Iterator;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.Vector;

/**
 *
 * @author Mr. Butkus
 */
public class FS {

RandomAccessFile file;
Word testWord = new Word();
Word EOFsymbol = new Word();
int blockSize = 256;
int allocationSegmentAddress = 0;
int headersSegmentAddress = blockSize;
int fileSegmentAddress = blockSize * 7;

public FS(RandomAccessFile file)
{
    this.file = file;
    this.EOFsymbol.a = '0';
    this.EOFsymbol.b = '0';
    this.EOFsymbol.c = '0';
    this.EOFsymbol.d = '$';
}

public void createFile() throws FileNotFoundException, IOException
{
    createFSstructure();
    
    if (createFile("Program1.txt", 1))
        System.out.println("good");
    else
        System.out.println("no good");

    if (createFile("Program2.txt", 1))
        System.out.println("good2");
    else
        System.out.println("no good2");

    open("Program1.txt");
    write("Program1.txt", stringToWordVector("Genys margas, pasaulis dar margesnis"));
    close("Program1.txt");
    
   // file.close();
}

public void createFSstructure() throws IOException
{
    byte b[] = new byte[10];
    char a = 'f';
    char c = '_';
    Word word = new Word();

    for (int i = 0; i < blockSize * blockSize; i++)
    {
        file.writeChar(word.a);
        file.writeChar(word.b);
        file.writeChar(word.c);
        file.writeChar(word.d);
    }
//  test stuff
//    file.writeChar('a');
//    file.writeChar('b');
//    file.writeChar('c');
//    file.writeChar('d');
//    file.writeChar('š');
//    file.writeChar('f');
//    file.writeChar('g');
//    file.writeChar('h');
//    file.writeChar('i');
//    file.writeChar('j');

}

public void shiftRight(int from, int howMany, int distance) throws IOException
{
    // adresacija baitis, char'as yra 2 baitu -- adresus dvigubinam
    byte[] b = new byte[howMany * 2];
    byte[] dist = new byte[distance * 2];
    file.seek(from * 2);
    file.readFully(b);
    file.readFully(dist);
    file.seek(from * 2);
    file.write(dist);
    file.write(b);
}

public void shiftLeft(int from, int howMany, int distance) throws IOException
{
    // adresacija baitis, char'as yra 2 baitu -- adresus dvigubinam
    byte[] b = new byte[howMany * 2];
    byte[] dist = new byte[distance * 2];
    file.seek(from * 2 - distance * 2);
    file.readFully(dist);
    file.readFully(b);
    file.seek(from * 2 - distance * 2);
    file.write(b);
    file.write(dist);
}

private Word getWordFromFS(int whichWord) throws IOException
{
    Word word = new Word();
    whichWord = whichWord * 2 * 4;
    file.seek(whichWord);
    word.a = file.readChar();
    word.b = file.readChar();
    word.c = file.readChar();
    word.d = file.readChar();
    return word;
}

private void setWordInFS(Word word, int position) throws IOException
{
    file.seek(position * 2 * 4);
    file.writeChar(word.a);
    file.writeChar(word.b);
    file.writeChar(word.c);
    file.writeChar(word.d);
}

private void createFH(String filename, int fileSize, int fileBeginingPoint) throws IOException
{
    Header ourFormedHeader = new Header();
    int i = 0;

    // find a 6-word abyss in 1..6 blocks
    while (!headerEmpty(getHeaderAt(i)))
    {
        i++;
    }

    // now, at i we have an empty header
    ourFormedHeader = formHeaderSructure(fileBeginingPoint, fileSize, filename);
    putHeader(ourFormedHeader, i);
}

private Header formHeaderSructure(int adr, int size, String filename)
{
    Header header = new Header();

    header.words[0] = intToDecWord(adr);
    header.words[1] = intToDecWord(size);
    header.words[2] = (Word) stringToWordVector(filename).get(0);       // potential bug herein ;]
    header.words[3] = (Word) stringToWordVector(filename).get(1);
    header.words[4] = (Word) stringToWordVector(filename).get(2);
    header.words[5] = intToDecWord(0);

    return header;
}

private void putHeader(Header header, int adr) throws IOException
{

    int headersBeginAt = 256;    // file headers are stored from the begining of block #1 (that is, second blcok);
    adr = adr * 6 + headersBeginAt;

    setWordInFS(header.words[0], adr);
    setWordInFS(header.words[1], adr + 1);
    setWordInFS(header.words[2], adr + 2);
    setWordInFS(header.words[3], adr + 3);
    setWordInFS(header.words[4], adr + 4);
    setWordInFS(header.words[5], adr + 5);

}

private Header getHeaderAt(int which) throws IOException
{
    Header header = new Header();
    int headersBeginAt = getHeadersSegmentAddress();    // file headers are stored from the begining of block #1 (that is, second blcok);
    which = which * 6 + headersBeginAt;
    header.words[0] = getWordFromFS(which);
    header.words[1] = getWordFromFS(which + 1);
    header.words[2] = getWordFromFS(which + 2);
    header.words[3] = getWordFromFS(which + 3);
    header.words[4] = getWordFromFS(which + 4);
    header.words[5] = getWordFromFS(which + 5);

    return header;
}

private Header getFH(String filename) throws IOException
{
    Header header = new Header();
    String currentFilename;
    int k;

    header = getHeaderAt(0);
    if (headerEmpty(header))
        return header;

    // processing first (position is 0) header
    currentFilename = wordToString(header.words[2]) +
            wordToString(header.words[3]) +
            wordToString(header.words[4]);
    if (currentFilename.compareTo(filename) == 0)
        return header;

    // processing other headers
    k = 0;
    while (currentFilename.compareTo(filename) != 0)
    {
        k++;
        header = getHeaderAt(k);
        if (headerEmpty(header))
            return header;
        else
            currentFilename = wordToString(header.words[2]) +
                    wordToString(header.words[3]) +
                    wordToString(header.words[4]);
    }
    return header;      // if we get here, header is the right one.
}

private boolean headerEmpty(Header header)
{
    boolean empty = true;
    for (int i = 0; i < 6; i++)
    {
        if (!wordEmpty(header.words[i]))
            empty = false;
    }

    return empty;
}

private int getHeaderNumber(String filename) throws IOException
{
    Header header = new Header();
    String currentFilename;
    int k;

    // no headers at all
    header = getHeaderAt(0);
    if (headerEmpty(header))
        return -1;

    // processing first (position is 0) header
    currentFilename = wordToString(header.words[2]) +
            wordToString(header.words[3]) +
            wordToString(header.words[4]);
    if (currentFilename.compareTo(filename) == 0)
        return 0;

    // processing other headers
    k = 0;
    while (currentFilename.compareTo(filename) != 0)
    {
        k++;
        header = getHeaderAt(k);
        if (headerEmpty(header))
            return -1;
        else
            currentFilename = wordToString(header.words[2]) +
                    wordToString(header.words[3]) +
                    wordToString(header.words[4]);
    }
    return k;      // if we get here, header is the right one.
}

private int getHeaderPosition(int headerNumber)
{
    if (headerNumber == -1)
        return -1;
    else
        return headerNumber * 6 + getHeadersSegmentAddress();
}

private void freeHeader(String filename) throws IOException
{
    int position = getHeaderPosition(getHeaderNumber(filename));
    Header header = new Header();

    // initiate header, so that it is not null, but a handful of words.
    // it doesn't matter what it is. next step changes the values.
    header = getHeaderAt(getHeaderPosition(getHeaderNumber(filename)));

    for (int i = 0; i < 6; i++)
    {
        header.words[i].a = 0;
        header.words[i].b = 0;
        header.words[i].c = 0;
        header.words[i].d = 0;
    }

    putHeader(header, getHeaderNumber(filename));
}

public static boolean wordEmpty(Word v)           // change after testing, of course
{
    if (v.a == 0 && v.b == 0 && v.c == 0 && v.d == 0)
        return true;
    else
        return false;
}

private boolean getOpen(String filename)
{
    // implementation goes here
    return true;
}

// Vector stuff -- vector of Words
private int writeToFile(String filename, int position, Vector stuff) throws IOException
{

    // jei nera tokio failo, do this
    if (headerEmpty(getFH(filename)))
        return -1;
    // jei failas uzdarytas (neatidarytas), do this
    else if (!getFileOpened(getFH(filename)))
        return -1;
    else
    {
        Header header = getFH(filename);
        int fileBegining = wordToInt(header.words[0]) * getBlockSize() + getFileSegmentAddress();

        position += fileBegining;
        // check if we fit in file boundaries
        if ((position + stuff.size()) <= (fileBegining + wordToInt(header.words[1]) * getBlockSize()))
        {
            // write
            for (int i = 0; i < stuff.size(); i++)
            {
                setWordInFS((Word) stuff.get(i), position);
                position++;
            }
            return 1;
        } else
            return -1;
    }

}

private Vector readFromFile(String filename, int position, int amount) throws IOException
{
    Vector stream = new Vector();   // wordų vektorius

    // jei nera tokio failo, grazinam tuscia vektoriu
    if (headerEmpty(getFH(filename)))
        return stream;
    // jei failas uzdarytas (neatidarytas), grazinam tuscia vektriu
    else if (!getFileOpened(getFH(filename)))
        return stream;
    else
    {
        Header header = getFH(filename);
        int fileBegining = wordToInt(header.words[0]) * getBlockSize() + getFileSegmentAddress();

        position += fileBegining;
        // check if we fit in file boundaries
        if ((position + amount) <= (fileBegining + wordToInt(header.words[1]) * getBlockSize()))
        {
            // read
            for (int i = 0; i < amount; i++)
            {
                stream.add(getWordFromFS(position));
                position++;
            }
            return stream;
        } else
            return stream;
    }
}

//private int deleteFile(String filename)
//{
//    // susirandam faila
//    //      1) jei nera, return -1
//    //      2) jei yra:
//    //          
//}




// use hasEnouthFreeBlocks first to ensure, we have enouth disk space in our FS
// -1 means can't do it. else returned value = first block of a sequence of blocks
//private int allocateBlocks(int howMany) throws IOException // sequential
//{
//    SortedSet set = new TreeSet();
//    int from = 0, to;
//    int amount;
//    Vector blocksToAllocate = new Vector();
//
////find unoccupied (word * howMany) in a row   -> return first block's # that meets
//    set = getUsedBlocks();
//    // Iterating over the elements in the set
//    Iterator it = set.iterator();
//    if (it.hasNext())
//        from = Integer.parseInt(it.next().toString());
//    else // empty disk -- no file's been created   ->   create at the begining + return;
//    {
//        for (int zz = 0; zz < howMany; zz++)
//        {
//            blocksToAllocate.add(zz);
//        }
//        setUsedBlocks(blocksToAllocate);
//        return 0;  // isskirtos atminties primojo bloko adresas yra 0;
//    }
//    while (it.hasNext())
//    {
//        to = Integer.parseInt(it.next().toString());
//        // do stuff
//        amount = to - from - 1;
//        // if found - > do other stuff and return;
//        if (amount >= howMany)
//        {
//            //allocate
//            for (int z = 0; z < howMany; z++)
//            {
//                blocksToAllocate.add(from + 1 + z);
//            }
//            setUsedBlocks(blocksToAllocate);
//            return from + 1;
//        }
//        from = to;
//    }
//    // if not found; should not ever get to here if you use hasEnouthFreeBlocks first. which you should.
//    //  NO. WHAT IF WE NEED  5 BLOCKS, AND EVERY SEDOND BLOCK IS OCCUPIED NOW.
//    //      THERE'S ENOUTH BLOCKS, BUT NOT A SINGLE MONOLITHICAL 5-BLOCK ABYSS.
//    return -1;
//
//}
private int allocateBlocks(int howMany) throws IOException // sequential
{
    SortedSet set = new TreeSet();
    int from = 0, to;
    int amount;
    Vector blocksToAllocate = new Vector();
    Vector blockNumbers = new Vector();  // blocks to allocate

    set = getUnusedBlocks();
    // if empty disk -- no file's been created   ->   doesn't matter. function will automatically allocate the file at the begining

    // Iterating over the elements in the set
    Iterator it = set.iterator();

    for (int dd = 0; dd < howMany; dd++)
    {
        if (it.hasNext())
            blockNumbers.add(Integer.parseInt(it.next().toString()));
        else
            return -1;
    }

    while (!isSequential(blockNumbers))
    {
        // shiftVectorLeft(blockNumbers);
        blockNumbers.remove(0);

        if (it.hasNext())
            blockNumbers.add(Integer.parseInt(it.next().toString()));
    }
    // sequential here or not found
    if (isSequential(blockNumbers))
    {
        // do stuff
        setUsedBlocks(blockNumbers);
        return Integer.valueOf(blockNumbers.get(0).toString());
    } else
        // imposible
        return -1;
}

private void freeBlocks(Header header) throws IOException
{
    Vector vector = new Vector();
    int begining = wordToInt(header.words[0]);
    int fileLength = wordToInt(header.words[1]);
    Word wordWithRealZeroes = new Word();


    // pasidarom vektoriu su atlaisvintinais bloku numeriais
    for (int i = begining; i < (begining + fileLength); i++)
    {
        vector.add(intToDecWord(i));
    }

    for (int i = 0; i < getBlockSize(); i++)
    {
        for (int j = 0; j < vector.size(); j++)
        {
            if(!wordEmpty(getWordFromFS(i))){
            if (wordToInt(getWordFromFS(i)) == wordToInt((Word) (vector.get(j))))
            {

                setWordInFS(wordWithRealZeroes, i);
            }
            }
        }
    }
}

private SortedSet getUsedBlocks() throws IOException  // int'u vektorius
{
    SortedSet ss = new TreeSet();
    int i = 0;

//    while (!wordEmpty(getWordFromFS(i)) && i < 256)
//    {
//        ss.add(wordToInt(getWordFromFS(i)));
//        i++;
//    }
    for(i = 0; i < blockSize; i++)
    {
        if(!wordEmpty(getWordFromFS(i)))
        {
            ss.add(wordToInt(getWordFromFS(i)));
        }
    }
    
    return ss;
}

private SortedSet getUnusedBlocks() throws IOException  // int'u vektorius
{
    SortedSet unused = new TreeSet();
    SortedSet used = new TreeSet();
    int i = 0;

    for (i = 0; i < 256; i++)
    {
        unused.add((int) i);
    }

    used = getUsedBlocks();
    Iterator it = used.iterator();
    for (i = 0; i < used.size(); i++)
    {
        unused.remove(it.next());
    }

    return unused;
}

private void setUsedBlocks(Vector tmp) throws IOException
{
    boolean done = false;
    int i = 0;

    for (int g = 0; g < tmp.size(); g++)
    {
        while (!wordEmpty(getWordFromFS(i)))
        {
            i++;
        }
        // radom tuscia lastele, i ja kalam allocate'intino bloko #
        setWordInFS(intToDecWord(Integer.parseInt(tmp.get(g).toString())), i);
    }
}

private boolean hasEnouthFreeBlocks(int needed) throws IOException
{
    SortedSet set = new TreeSet();
    set = getUsedBlocks();
    int count = 0;

    // Iterating over the elements in the set
    Iterator it = set.iterator();
    while (it.hasNext())
    {
        Object element = it.next();
        count++;
    }

    if (256 - count >= needed)
        return true;
    else
        return false;
}

private boolean getFileOpened(Header header)
{
    if (wordToInt(header.words[5]) == 1)
        return true;
    else
        return false;
}

private boolean setFileOpened(String filename, boolean state) throws IOException
{
    int isOpen;

    if (state)
        isOpen = 1;
    else
        isOpen = 0;

    int position = getHeaderPosition(getHeaderNumber(filename));
    if (position == -1)
        return false;
    else
    {
        // let's get word which contains isOpen value
        position += 5;
        setWordInFS(intToDecWord(isOpen), position);
        return true;
    }


}

/////// METHODS FOR VM  //////////
//////////////////////////////////

public boolean createFile(String filename, int desiredFileSize) throws IOException
{
    int fileBegining;
// check if exists
//      yes: return false -- can't create.   getFH(filename) -> not empty -> can't
//      no:     getFH(filename) -> empty -> no such file -> free to create
//          1. allocate a block(s)   
//          2. allocate file-header and place it.
    if (!headerEmpty(getFH(filename)))
        // file with this filename already exsists
        return false;
    else
    {
        fileBegining = allocateBlocks(desiredFileSize);
        if (fileBegining == -1)
            // can't allocate desired blocks
            return false;
        else
        {
            createFH(filename, desiredFileSize, fileBegining);
            setWordInFS(EOFsymbol, fileBegining * getBlockSize() + getFileSegmentAddress());
            return true;
        }
    }
}

public int deleteFile(String filename) throws IOException
{

    // randam faila
    //      1) neradom -> return -1;
    //      2) radom
    //          jei atidarytas -> return -1;
    //          jei uzdarytas
    //               istrinti headeri
    //               atlaisvinti blokus.

    Header header = new Header();

    header = getFH(filename);
    if (headerEmpty(header))
        return -1;
    else if (getFileOpened(header))
        return -1;
    else
    {
        freeBlocks(header);
        freeHeader(filename);
        return 1;
    }
}

public void open(String filename) throws IOException
{
    setFileOpened(filename, true);
}

public void close(String filename) throws IOException
{
    setFileOpened(filename, false);
}

public Vector read(String filename) throws IOException
{
    Vector vector = new Vector();
    int positionOfEOFsymbol;
    int amount = 0;
    boolean EOFsymbolFound = false;
    Word zz = new Word();
    Word qq = new Word();

    // get $ position
    positionOfEOFsymbol = wordToInt(getFH(filename).words[0]) * getBlockSize() + getFileSegmentAddress();
    while (!EOFsymbolFound)
    {
        zz = getWordFromFS(positionOfEOFsymbol);
        qq = EOFsymbol;
        if (getWordFromFS(positionOfEOFsymbol).d == EOFsymbol.d)
            EOFsymbolFound = true;
        else
        {
            positionOfEOFsymbol++;
            amount++;
        }

    }

    vector = readFromFile(filename, 0, amount);
    return vector;
}

// in stack pointer there is address pointing to the file to write to
// and operand points to memory address where stuff to write is
public void write(String filename, Vector stuff) throws IOException
{
    int positionForEOFsymbol;

    if (writeToFile(filename, 0, stuff) == 1)
    {
        positionForEOFsymbol = wordToInt(getFH(filename).words[0]) * getBlockSize() + getFileSegmentAddress() + stuff.size();
        setWordInFS(EOFsymbol, positionForEOFsymbol);
    }
}
////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////

/////// AUXILIARY METHODS ///////////
private Vector stringToWordVector(String str)
{
    Vector vector = new Vector();
    Word word[];
    char[] tmp = null;
    int length;
    int charsToAdd;     // string/4 must be = 0; cause word = 4 chars. this is to compensate.

    // makes string dividable by 4
    length = str.length();
    if (length % 4 > 0)
    {
        charsToAdd = 4 - (length % 4);
        for (int i = 0; i < charsToAdd; i++)
        {
            str += String.valueOf(0);
        }
    }

    word = new Word[str.length() / 4];
    for (int aa = 0; aa < str.length() / 4; aa++)
    {
        word[aa] = new Word();
    }
    tmp = str.toCharArray();
    for (int g = 0; g < tmp.length / 4; g++)
    {
        // Jei priskytei idejai i Vektoriu elementa, po to ji pakeitai ir dar pridejai. turesi ne skirtingus, o vienodus elementus Vektoriuje.
        word[g].a = tmp[g * 4];
        word[g].b = tmp[g * 4 + 1];
        word[g].c = tmp[g * 4 + 2];
        word[g].d = tmp[g * 4 + 3];
        vector.add(word[g]);
    }
    return vector;
}

private String wordVectorToString(Vector wv)
{
    Vector vector = wv;
    String string = "";
    for (int i = 0; i < vector.size(); i++)
    {
        string += wordToString((Word) vector.get(i));
    }

    return string;
}

public Vector[] getAllFilenames() throws IOException
{
    Vector[] allTheStuff = new Vector[2];
    Vector headers = new Vector();
    Vector states = new Vector();
    Vector names = new Vector();
    int i = 0;
    
    // get all headers
    while(!headerEmpty(getHeaderAt(i)))
    {
        headers.add(getHeaderAt(i));
        states.add(getFileOpened(getHeaderAt(i)));
        names.add(wordToString(getHeaderAt(i).words[2]) + 
                  wordToString(getHeaderAt(i).words[3]) + 
                  wordToString(getHeaderAt(i).words[4])  );
                
        i++;
    }
    
    allTheStuff[0] = names;
    allTheStuff[1] = states;
    
    // parse filenames and add to vector
    return allTheStuff;
}

public String wordToString(Word w)
{
    char[] asdf = new char[4];
    asdf[0] = w.a;
    asdf[1] = w.b;
    asdf[2] = w.c;
    asdf[3] = w.d;
    return String.valueOf(asdf);
}

public int wordToInt(Word w)
{
    int s = 0;
    s = Integer.valueOf(String.valueOf(w.a)) * 1000 + Integer.valueOf(String.valueOf(w.b)) * 100 + Integer.valueOf(String.valueOf(w.c)) * 10 + Integer.valueOf(String.valueOf(w.d));
    return s;
}

public Word intToDecWord(int s)
{
    Word w = new Word();
    s = s % 10000;
    w.a = Integer.toString(s / 1000).charAt(0);
    w.b = Integer.toString(s % 1000 / 100).charAt(0);
    w.c = Integer.toString(s % 100 / 10).charAt(0);
    w.d = Integer.toString(s % 10).charAt(0);
    return w;
}

// jei 0 arba 1 blokas -> true, else -> this function determines
private boolean isSequential(Vector vector)
{
    boolean is = true;
    int first, second;

    if (vector.size() < 2)
        return true;

    first = Integer.valueOf(vector.get(0).toString());
    for (int i = 1; i < vector.size(); i++)
    {
        second = Integer.valueOf(vector.get(i).toString());
        if (!(first + 1 == second))
            return is = false;
        else
            first = second;
    }
    return is;
}

private int getAllocationSegmentAddress()
{
    return this.allocationSegmentAddress;
}

private int getHeadersSegmentAddress()
{
    return this.headersSegmentAddress;
}

private int getFileSegmentAddress()
{
    return this.fileSegmentAddress;
}

private int getBlockSize()
{
    return this.blockSize;
}
}
